#include <windows.h>
#include <stdio.h>
#include <time.h>
#include <stdlib.h>
#include <string.h>

#define N 20

int GetKey(int n, int Key)
{
	//n表示循环次数，即总停顿多长时间，Key为返回的键盘输入
	//键盘录取函数
    int sign = 0;
    for(int i = 0; i < n; i++)
    {
    	//sign为0说明没有录取到键盘输入，sign为1时键盘已经录入，不需要再次录入
        if(sign == 0)
        {
            if (GetAsyncKeyState('W') & 0x8000)
		    {
		    	if(Key == 3)
					goto a;
                Key = 1;
                sign = 1;
            }
            if (GetAsyncKeyState('A') & 0x8000)
		    {
		    	if(Key == 4)
					goto a;
                Key = 2;
                sign = 1;
            }
            if (GetAsyncKeyState('S') & 0x8000)
		    {
		    	if(Key == 1)
					goto a;
                Key = 3;
                sign = 1;
            }
            if (GetAsyncKeyState('D') & 0x8000)
		    {
		    	if(Key == 2)
					goto a;
		        Key = 4;
                sign = 1;
		    }
		    if (GetAsyncKeyState(VK_F1) & 0x8000)
		    {
		        Key = 5;
                sign = 1;
		    }
			if (GetAsyncKeyState(VK_F2) & 0x8000)
		    {
		        Key = 6;
                sign = 1;
		    }
            
        }
        if (GetAsyncKeyState(VK_SPACE) & 0x8000)
	    {
	        return 0;
	    }a:;
        Sleep(15); //暂停15毫秒
    }
    return Key;
}

void GamePrint(char Map[N-1][N], int score, HANDLE hBuffer)
{
    //定义 CHAR_INFO 缓冲区（存储字符 + 颜色）
    CHAR_INFO charBuffer[N + 2][2*N + 2];

    //填充 CHAR_INFO 缓冲区
    //颜色内容：边框：绿色
    //          背景：白色
    //          蛇：黄色
    //          食物：红色
    //得分显示
    char scoreStr[10];
	sprintf(scoreStr, "Score: %d", score);
    for(int i = 0; i < 2*N+2; i++)
	{
		if(i <= (int)strlen(scoreStr) && i > 0)
			charBuffer[0][i].Char.AsciiChar = scoreStr[i-1];
		else
			charBuffer[0][i].Char.AsciiChar = ' ';
		charBuffer[0][i].Attributes = FOREGROUND_RED | FOREGROUND_GREEN | FOREGROUND_BLUE;
	}
    //上边界
	for(int i = 0; i < 2*N+2; i++)
	{
		charBuffer[1][i].Char.AsciiChar = '-';
		charBuffer[1][i].Attributes = FOREGROUND_GREEN;
	}
	//地图内容
    for (int i = 0; i < N-1; i++)
	{
        //左边界
        charBuffer[i+2][0].Char.AsciiChar = '|';
        charBuffer[i+2][0].Attributes = FOREGROUND_GREEN;

        //地图内容
        for(int j = 0; j < N; j++)
		{
            charBuffer[i+2][2*j+1].Char.AsciiChar = Map[i][j];
            if (Map[i][j] == '@' || Map[i][j] == '*')
                charBuffer[i+2][2*j+1].Attributes = FOREGROUND_RED | FOREGROUND_GREEN | 
				                                    BACKGROUND_RED | BACKGROUND_GREEN | BACKGROUND_BLUE;
            else if (Map[i][j] == 'O')
                charBuffer[i+2][2*j+1].Attributes = FOREGROUND_RED | 
				                                    BACKGROUND_RED | BACKGROUND_GREEN | BACKGROUND_BLUE;
            else
            	charBuffer[i+2][2*j+1].Attributes = FOREGROUND_RED | FOREGROUND_GREEN | FOREGROUND_BLUE | 
				                                    BACKGROUND_RED | BACKGROUND_GREEN | BACKGROUND_BLUE;
            charBuffer[i+2][2*j+2].Char.AsciiChar = ' ';
            charBuffer[i+2][2*j+2].Attributes = FOREGROUND_RED | FOREGROUND_GREEN | FOREGROUND_BLUE |
			                                    BACKGROUND_RED | BACKGROUND_GREEN | BACKGROUND_BLUE;
        }
        //右边界
        charBuffer[i+2][2*N+1].Char.AsciiChar = '|';
		charBuffer[i+2][2*N+1].Attributes = FOREGROUND_GREEN;
    }
    //下边界
	for(int i = 0; i < 2*N+2; i++)
	{
		charBuffer[N+1][i].Char.AsciiChar = '-';
		charBuffer[N+1][i].Attributes = FOREGROUND_GREEN;
	}

    COORD bufferSize = {2*N + 2, N + 2};    //定义写入区域
    COORD bufferCoord = {0, 0};      //从 CHAR_INFO 缓冲区的 (0,0) 开始读取
    SMALL_RECT writeRegion = {0, 0, 2*N + 1, N + 1};   //控制台写入区域

    //使用双缓冲一次性写入字符和颜色
    WriteConsoleOutput(
        hBuffer,    //控制台的屏幕缓冲区（直接输出的缓冲区）
        (CHAR_INFO*)charBuffer,     // CHAR_INFO 缓冲区（预先准备的写入了字符和颜色的缓冲区）
        bufferSize,    //写入的区域
        bufferCoord,    //读取起始点
        &writeRegion    //控制台写入区域
    );

    //重置光标
    SetConsoleCursorPosition(hBuffer, (COORD){0, 0});
}

void GameLogic(HANDLE hBuffer)
{
    //定义并清洗地图
    char Map[N-1][N];
    for(int i = 0; i < N-1; i++)
	{
		for(int j = 0; j < N; j++)
		{
			Map[i][j] = ' ';
		}
	}
    //定义蛇的长度及所有部位的坐标
    int length = 3, snake[N*N][2];
	//定义初始蛇各个部位的坐标
    snake[0][0] = N/2; snake[0][1] = N/2;
    snake[1][0] = N/2 + 1; snake[1][1] = N/2;
    snake[2][0] = N/2 + 2; snake[2][1] = N/2;
    //定义食物的坐标,初始方向，初始分数以及初始速度
    int x, y, direction = 1, score = 0, speed = 10;
    //随机生成食物
    srand(time(NULL));
    x = rand() % (N-1);
    y = rand() % N;
    Map[x][y] = 'O';
    //划归蛇的位置
	Map[snake[0][0]][snake[0][1]] = '@';
	Map[snake[1][0]][snake[1][1]] = '*';
	Map[snake[2][0]][snake[2][1]] = '*';
	
	Sleep(2000);
    //打印地图
	GamePrint(Map, score, hBuffer);
    while (1)
	{
        //蛇的变化：将尾巴置为空格，所有部位向前移动一个，覆盖掉原本头的位置
		//保存蛇尾的位置，便于失败后恢复蛇尾以及增加长度
        Map[snake[length-1][0]][snake[length-1][1]] = ' ';
		int tail[2] = {snake[length-1][0], snake[length-1][1]};
        for(int i = length-1; i > 0; i--)
		{
			snake[i][0] = snake[i-1][0];
			snake[i][1] = snake[i-1][1];
		}
        //根据存储的方向确定头的下一个位置
        switch (direction)
        {
        case 1:
            snake[0][0]--;
            break;
        case 2:
            snake[0][1]--;
            break;
        case 3:
            snake[0][0]++;
            break;
        case 4:
            snake[0][1]++;
            break;
        default:
            break;
        }
        //边界限制
        if(snake[0][0] < 0 || snake[0][0] >= N-1 || snake[0][1] < 0 || snake[0][1] >= N || Map[snake[0][0]][snake[0][1]] == '*')
		{
			//结束界面(选择界面3)
			return;
		}
        //检测是否吃到食物
        if (Map[snake[0][0]][snake[0][1]] == 'O')
		{
			//得分增加
			if(speed > 10)
			{
				score+=1;
			}
			else
			{
				score+=(2*(11-speed));
			}
			//速度增加
			speed = 2>speed-1?2:speed-1;
            //吃到食物后增加蛇的长度，增加的部分即原来的蛇尾
            length++;
            Map[tail[0]][tail[1]] = '*';
            snake[length-1][0] = tail[0]; snake[length-1][1] = tail[1];
			//确保食物的生成合法
			while(1)
			{
				x = rand() % (N-1);
				y = rand() % N;
				if(Map[x][y] == ' ')
				{
					Map[x][y] = 'O';
					break;
				}
			}
		}
		//重新划归蛇的位置,划归下一个蛇头，以及将接替蛇头的位置的蛇头划归为蛇身
    	Map[snake[0][0]][snake[0][1]] = '@';
		Map[snake[1][0]][snake[1][1]] = '*';
		//打印地图
        GamePrint(Map, score, hBuffer);
        //记录按键
        int temp = GetKey(speed, direction);
        //检测Esc键状态
        if(temp == 0)
		{
			return;
		}
		//检测加减速
		else if(temp == 5)
		{
			speed = 2>speed-1?2:speed-1;
		}
		else if(temp == 6)
		{
			speed = 20<speed+2?20:speed+2;
		}
		//检测方向
		else
        	direction = temp;

    }	
}

int main()
{
	//定义双缓冲的句柄
	HANDLE hBuffer = CreateConsoleScreenBuffer(
	    GENERIC_READ | GENERIC_WRITE,	//缓冲区设置为可读（输出）可写（改变状态）
	    FILE_SHARE_READ | FILE_SHARE_WRITE,
	    NULL,	//默认安全属性
	    CONSOLE_TEXTMODE_BUFFER,	//应为程序由字符呈现，故采用文本模式的缓冲区
	    NULL	//无模板
	);
	SetConsoleActiveScreenBuffer(hBuffer); 		//设置为活动缓冲区
	
	GameLogic(hBuffer);

    //释放资源
    SetConsoleActiveScreenBuffer(GetStdHandle(STD_OUTPUT_HANDLE));
	CloseHandle(hBuffer);
    printf("程序结束\n");
    return 0;
}
